﻿using System;
using System.Collections.Generic;
using System.Linq;
using GeneticAlgorithms.Factories;
using GeneticAlgorithms.Genomes;
using GeneticAlgorithms.Operators.Crossover;
using GeneticAlgorithms.Operators.Fitness;
using GeneticAlgorithms.Operators.Mating;
using GeneticAlgorithms.Operators.Mutation;
using GeneticAlgorithms.Operators.Selection;
using GeneticAlgorithms.Populations;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System.Data.Odbc;
using GeneticAlgorithms.Alphabets;

namespace GeneticAlgorithms.Tests
{
    
    
    /// <summary>
    ///This is a test class for PopulationBaseTest and is intended
    ///to contain all PopulationBaseTest Unit Tests
    ///</summary>
    [TestClass()]
    public abstract class PopulationBaseTest
    {
        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region Additional test attributes
        // 
        //You can use the following additional attributes as you write your tests:
        //
        //Use ClassInitialize to run code before running the first test in the class
        //[ClassInitialize()]
        //public static void MyClassInitialize(TestContext testContext)
        //{
        //}
        //
        //Use ClassCleanup to run code after all tests in a class have run
        //[ClassCleanup()]
        //public static void MyClassCleanup()
        //{
        //}
        //
        //Use TestInitialize to run code before running each test
        //[TestInitialize()]
        //public void MyTestInitialize()
        //{
        //}
        //
        //Use TestCleanup to run code after each test has run
        //[TestCleanup()]
        //public void MyTestCleanup()
        //{
        //}
        //
        #endregion


        private PopulationBase<ArrayGenome<TGene>, TGene> CreatePopulationBase<TGene>()
        {
            return CreatePopulationBase<TGene>(2);
        }

        [TestMethod()]
        /*
        [DataSource(
            "System.Data.Odbc", // The provider
            @"Dsn=Excel Files;dbq=C:\Documents and Settings\Diogo\My Documents\Visual Studio 2008\Projects\Prototypes\ExcelLoadTest\ExcelLoadTest\StatsReference.xlsx;defaultdir=C:\Documents and Settings\Diogo\My Documents\Visual Studio 2008\Projects\Prototypes\ExcelLoadTest\ExcelLoadTest;driverid=1046;maxbuffersize=2048;pagetimeout=5",
            "Stats$",      // The table name, in this case, the sheet name with a '$' appended.
            DataAccessMethod.Sequential)]
         */
        public void UpdateStatsTest()
        {
            // TODO: fix this ugly kludge for some issues with the data-driven unit test later on.
            using (OdbcConnection connection = new OdbcConnection(@"Driver={Microsoft Excel Driver (*.xls)};Dsn=Excel Files;dbq=StatsReference.xls;defaultdir=.;driverid=1046;maxbuffersize=2048;pagetimeout=5"))
            {
                OdbcCommand command = new OdbcCommand();
                command.Connection = connection;
                command.CommandText = "SELECT * FROM [Stats$]";
                connection.Open();
                OdbcDataReader reader = command.ExecuteReader();
                while (reader.Read())
                {
                    List<ArrayGenome<bool>> genomes = new List<ArrayGenome<bool>>();
                    double? average = null;
                    double? variance = null;
                    double? best = null;
                    for (int i = 0; i < reader.FieldCount; ++i)
                    {
                        string currentColName = reader.GetName(i);
                        if (currentColName.Contains("Value"))
                        {
                            ArrayGenome<bool> newGenome = new ArrayGenome<bool>(new BinaryAlphabet(), 10);
                            newGenome.Fitness = (double)reader[i];
                            genomes.Add(newGenome);
                        }
                        else
                        {
                            switch (currentColName)
                            {
                                case "Average":
                                    average = (double)reader[i];
                                    break;
                                case "Variance":
                                    variance = (double)reader[i];
                                    break;
                                case "Best":
                                    best = (double)reader[i];
                                    break;
                                default:
                                    Assert.Inconclusive("Column {0} in the excel file was not expected.", currentColName);
                                    break;
                            }
                        }
                    }

                    if (!(average.HasValue && variance.HasValue && best.HasValue))
                    {
                        Assert.Inconclusive("At least one of the result columns is not present.");
                    }

                    if (genomes.Count == 0)
                    {
                        Assert.Inconclusive("No value column found.");
                    }

                    PopulationBase<ArrayGenome<bool>, bool> target = CreatePopulationBase<bool>(genomes.Count);

                    for (int i = 0; i < target.Count; ++i)
                    {
                        target[i] = genomes[i];
                    }

                    target.UpdateStats();
                    int precision = 10;
                    Assert.AreEqual<double>(Math.Round(average.Value, precision), Math.Round(target.AverageFitness.Value, precision));
                    Assert.AreEqual<double>(Math.Round(variance.Value, precision), Math.Round(target.Variance.Value, precision));
                    Assert.AreEqual<double>(Math.Round(best.Value, precision), Math.Round(target.BestFitness, precision));
                }
                connection.Close();
            }
        }

        [Ignore]
        [TestMethod()]
        public void SortTest()
        {
        }

        [TestMethod()]
        [DeploymentItem("GeneticAlgorithms.dll")]
        public abstract void MergeTest();

        [Ignore]
        [TestMethod()]
        public void InitializeTest()
        {
        }

        [TestMethod()]
        [DeploymentItem("GeneticAlgorithms.dll")]
        public void GetBestTest()
        {
            PopulationBase<ArrayGenome<bool>, bool> target = CreatePopulationBase<bool>(30);
            Random rnd = new Random();
            for (int i = 0; i < target.Count; ++i)
            {
                target[i].Fitness = rnd.NextDouble() * 100;
            }
            double expectedMax = target.Max(g => g.Fitness.Value);
            var best = target.GetBest();
            Assert.IsNotNull(best);
            Assert.AreEqual(expectedMax, best.Fitness);
        }

        private PopulationBase_Accessor<ArrayGenome<T>, T> CreateAccessor<TGenome, T>(int populationSize)
        {
            PopulationBase<ArrayGenome<T>, T> target = CreatePopulationBase<T>(populationSize);
            return new PopulationBase_Accessor<ArrayGenome<T>, T>(new PrivateObject(target));            
        }

        protected abstract PopulationBase<ArrayGenome<TGene>, TGene> CreatePopulationBase<TGene>(int populationSize);

        [TestMethod()]
        public void DoStepTest()
        {
            PopulationBase<ArrayGenome<int>, int> target = CreatePopulationBase<int>();

            #region Creating mocks

            MockFactory factory = new MockFactory(MockBehavior.Strict);
            Mock<IGeneticSelector<ArrayGenome<int>, int>> selectorMock = 
                factory.Create<IGeneticSelector<ArrayGenome<int>, int>>();
            Mock<IGeneticCrossover<ArrayGenome<int>, int>> crossoverMock = 
                factory.Create<IGeneticCrossover<ArrayGenome<int>, int>>();
            Mock<IGeneticMutator<int>> mutatorMock = factory.Create<IGeneticMutator<int>>();
            Mock<IGeneticMater<ArrayGenome<int>, int>> materMock = 
                factory.Create<IGeneticMater<ArrayGenome<int>, int>>();
            Mock<IFitnessProvider<ArrayGenome<int>, int>> fitnessMock = 
                factory.Create<IFitnessProvider<ArrayGenome<int>, int>>();
            Mock<IGenomeFactory<ArrayGenome<int>>> factoryMock = 
                factory.Create<IGenomeFactory<ArrayGenome<int>>>();

            #endregion

            #region Setting up expectations

            var selectedGenomes = GetSelectedGenomes<int>(target);
            
            selectorMock.Expect(s => s.SelectGenomes(target, target.NewGenerationSize))
                .Returns(selectedGenomes);

            crossoverMock.Expect(c => c.Cardinality).Returns(2);

            var pairs = GetPairs(selectedGenomes);
            materMock.Expect(m => m.Mate(selectedGenomes)).Returns(pairs);

            foreach (var pair in pairs)
            {
                ArrayGenome<int>[] children = new ArrayGenome<int>[2] 
                    { CreateMockGenome<int>("C"), CreateMockGenome<int>("D") }; 

                int i = 0;
                foreach (var child in children)
                {
                    factoryMock.Expect(f => f.CreateGenome()).Returns(() => children[i++]);
                }

                crossoverMock.Expect(cross =>
                    cross.Cross(
                        //It.Is<IGenome<int>[]>(p => p.IsEqual(pair)), 
                        //It.Is<IGenome<int>[]>(c => c.IsEqual(children)))).
                        It.Is<ArrayGenome<int>[]>(p => p.SequenceEqual(pair)),
                        It.Is<ArrayGenome<int>[]>(c => c.SequenceEqual(children)))).
                    Callback(() => FillChildren<int>(children));

                mutatorMock.Expect(m => m.Mutate(children[0]));
                mutatorMock.Expect(m => m.Mutate(children[1]));
                fitnessMock.Expect(f => f.CalculateFitness(children[0])).Returns(1);
                fitnessMock.Expect(f => f.CalculateFitness(children[1])).Returns(1);
            }

            #endregion

            #region Setting up mocks
            try
            {
                target.Selector = selectorMock.Object;
                target.Mater = materMock.Object;
                target.Crossover = crossoverMock.Object;
                target.Mutator = mutatorMock.Object;
                target.FitnessProvider = fitnessMock.Object;
                target.GenomeFactory = factoryMock.Object;
            }
            catch
            {
                throw;
            }
            #endregion

            target.DoStep();

            factory.VerifyAll();
        }

        private void FillChildren<T>(IGenome<T>[] children)
        {
            children[0] = CreateMockGenome<T>("C");
            children[1] = CreateMockGenome<T>("D");
        }

        protected virtual IEnumerable<ArrayGenome<T>[]> GetPairs<T>(IList<ArrayGenome<T>> selectedGenomes)
        {
            yield return new ArrayGenome<T>[2] { selectedGenomes[0], selectedGenomes[1] };
        }

        protected virtual IList<ArrayGenome<T>> GetSelectedGenomes<T>(PopulationBase<ArrayGenome<T>, T> population)
        {
            return new ArrayGenome<T>[2] { CreateMockGenome<T>("A"), CreateMockGenome<T>("B") };
        }

        private ArrayGenome<T> CreateMockGenome<T>(string tag)
        {
            return new MockGenome<T>(tag);
        }

        protected class MockGenome<T> : ArrayGenome<T>, IEquatable<MockGenome<T>>
        {
            public MockGenome(string tag)
                : base(null, 0)
            {
                Tag = tag;
            }

            public string Tag { get; set; }

            public override bool Equals(object obj)
            {
                return Equals((MockGenome<T>) obj);
            }

            public override int GetHashCode()
            {
                return Tag.GetHashCode();
            }

            public override string ToString()
            {
                return "Mock genome: " + Tag;
            }
            #region IGenome<TItem> Members

            #endregion

            #region IEquatable<MockGenome<TItem>> Members

            public bool Equals(MockGenome<T> other)
            {
                return this.Tag.Equals(other.Tag);
            }

            #endregion
        }
    }
}
